/*
 * @Author: hongbin
 * @Date: 2023-09-15 10:54:43
 * @LastEditors: hongbin
 * @LastEditTime: 2023-09-27 20:29:40
 * @Description:ctrl 跳转组件
 */
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";

export class GoDefinitionProvider implements vscode.DefinitionProvider {
  extArr = [".vue", ".js"];
  /**
   * 0: "import ui from '../component/ui/index.vue"
   * 1: "ui"
   * 2: "../component/ui/index.vue"
   * 3: ".vue"
   */
  static reg = new RegExp(
    /import\s*([\s|\w|*|\{|\,|\}]*)\s+from\s*[\'|\"]([\.|\@|\/]+[\w|\/|\-]+(\.\w+)*)/
  );
  static tagReg = new RegExp(/<\/*([\w|-]+)/);
  static grammarFragment: string[];
  static filePath: string;
  static importAnalysis: Record<
    string,
    (typeof GoDefinitionProvider)["grammarFragment"]
  >;
  static prevInnerText: string;
  static componentsInnerText: string;
  static exportReg = new RegExp(/\s*export\s*default\s*{\s*/);
  static scriptReg = new RegExp(/\s*<script>\s*/);

  static handleFilePath(document: vscode.TextDocument) {
    //别名导入  @/xxx @识别为src
    if (GoDefinitionProvider.grammarFragment[2][0] == "@") {
      const absolute = GoDefinitionProvider.grammarFragment[2].replace(
        /@/,
        "src"
      );
      GoDefinitionProvider.filePath = path
        .dirname(document.uri.path)
        .replace(/src\/.+/, absolute);
    }
    //常规导入 ../xxx
    else {
      //: 要从文件夹名称开始拼接 否则少一级
      GoDefinitionProvider.filePath = path.resolve(
        path.dirname(document.uri.path),
        GoDefinitionProvider.grammarFragment[2]
      );
    }
  }

  static matchImport = (
    target: Object,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    const callback = descriptor.value;

    descriptor.value = function (
      document: vscode.TextDocument,
      position: vscode.Position,
      token: vscode.CancellationToken
    ) {
      const lineText = document.lineAt(position).text;
      let grammarFragment = [
        ...(lineText.match(GoDefinitionProvider.reg) ?? []),
      ];
      if (!grammarFragment.length) {
        const tag = lineText.match(GoDefinitionProvider.tagReg);
        if (tag) {
          const word = document.getText(
            document.getWordRangeAtPosition(position)
          );
          if (word === tag[1]) {
            //查找组件是否有导入字样
            GoDefinitionProvider.findImportSyntax(document, position);
            const fragment = GoDefinitionProvider.importAnalysis[word];
            if (fragment) {
              console.log("找到啦🎉", fragment);
              grammarFragment = fragment;
            } else {
              const regStr = new RegExp(
                `(\\w+)\\s*:\\s*[\\'|\\"]+${word}[\\'|\\"]+`
              );
              const res =
                GoDefinitionProvider.componentsInnerText.match(regStr);
              if (res && res[1]) {
                grammarFragment =
                  GoDefinitionProvider.importAnalysis[res[1]] || [];
              } else
                console.log(
                  word + "没找到😭",
                  GoDefinitionProvider.importAnalysis
                );
            }
          }
        }
      }

      if (grammarFragment.length) {
        GoDefinitionProvider.grammarFragment = grammarFragment;
        GoDefinitionProvider.handleFilePath(document);

        return callback.call(this, document, position, token);
      }
    };
  };

  @GoDefinitionProvider.matchImport
  provideDefinition(): vscode.Location | undefined {
    //1.1 有后缀名
    if (GoDefinitionProvider.grammarFragment[3]) {
      return this.handlePath(GoDefinitionProvider.filePath);
    }
    //1.2 没有后缀名 遍历extArr查找一个存在的路径 或者是文件夹则查找index.xxx
    else {
      const existsPath = this.appendExtFindExistsPath(
        GoDefinitionProvider.filePath
      );
      return this.handlePath(existsPath);
    }
  }

  appendExtFindExistsPath(path: string) {
    for (let index = 0; index < this.extArr.length; index++) {
      const ext = this.extArr[index];
      // console.log(path + ext, fs.existsSync(path + ext));
      if (fs.existsSync(path + ext)) return path + ext;
      // 文件夹则自动查找 index.xxx文件
      if (fs.existsSync(path + "/index" + ext)) return path + "/index" + ext;
    }
  }

  handlePath(filePath?: string) {
    if (filePath && fs.existsSync(filePath)) {
      return new vscode.Location(
        vscode.Uri.file(filePath),
        new vscode.Position(0, 0)
      );
    }
  }

  //一行一行向下遍历查找导入语句 分析对应组件关系 以及components转换关系 找到组件的位置
  static findImportSyntax(
    document: vscode.TextDocument,
    position: vscode.Position
  ) {
    let innerText = ``;
    let record = false;
    let _position = new vscode.Position(position.line, 0);

    //找导入语句
    while (document.lineCount > _position.line) {
      const lineText = document.lineAt(_position).text;
      if (GoDefinitionProvider.exportReg.test(lineText)) break;
      if (record) {
        innerText += lineText;
      }
      if (GoDefinitionProvider.scriptReg.test(lineText)) record = true;
      _position = _position.translate(1);
    }

    if (GoDefinitionProvider.prevInnerText !== innerText) {
      GoDefinitionProvider.prevInnerText = innerText;

      // const imports = innerText.match(/import.*[\"|\']/);
      const imports = [
        ...innerText.matchAll(
          /import\s*([\s|\w|*|\{|\,|\}]*)\s+from\s*[\'|\"]([\.|\@|\/]+[\w|\/|\-]+(\.\w+)*)/g
        ),
      ];
      GoDefinitionProvider.analysis(imports);
    } else {
      console.log("import部分没有变动");
    }

    //找components对应关系
    const componentsReg = new RegExp(/components\:\s*\{/);
    const endComponentsReg = new RegExp(/\s*\}\s*/);
    let componentsInnerText = ``;
    record = false;
    while (document.lineCount > _position.line) {
      const lineText = document.lineAt(_position).text;
      if (record && endComponentsReg.test(lineText)) break;
      if (componentsReg.test(lineText)) record = true;
      if (record) {
        componentsInnerText += lineText;
      }
      _position = _position.translate(1);
    }
    GoDefinitionProvider.componentsInnerText = componentsInnerText;
  }

  /**import * as xx from '../xxx' */
  private static 别名导入 = new RegExp(/as /);
  /**import xx from '../xxx' */
  private static 常规导入 = new RegExp(/\w*/);
  /**import {xx as xxxx,xxx} from '../xxx' */
  private static 多导入 = new RegExp(/()/);

  /**
   * 解析import语句
   */
  static analysis(imports: string[][]) {
    const result: Record<string, string[]> = {};

    imports.forEach(([total, importSection, filePath], index) => {
      const normal = importSection.match(GoDefinitionProvider.常规导入);
      console.log("importSection:", importSection);
      console.log(normal);
      if (normal && normal[0] === importSection) {
        result[importSection] = imports[index];
      }
    });

    GoDefinitionProvider.importAnalysis = result;
  }
}
